[Spring-plus] 의존성 주입 (Spring-DI)
😎

[Spring-plus] 의존성 주입 (Spring-DI)

Lecture
Framework
태그
dev
spring
public
완성
Y
생성일
Mar 17, 2024 01:37 PM
LectureName
Spring

1. 의존관계 주입이란?

사용
의존관계 주입(Dependency Injection), 의존관계 주입은 객체를 생성하고, 의존하는 객체를 직접 생성하지 않고, 생성된 객체를 주입하여 사용합니다.
 
Spring에서의 DI
  1. 생성자 주입
  1. Setter 주입
  1. 필드 주입
  1. 일반 메서드 주입
많은 방법들이 있지만 거의 생성자 주입 혹은 Setter 주입을 사용하게 됩니다.
 

2. DI 방법 및 예시

생성자 주입
  • 생성자 호출 시점에 딱 한번만 호출된다.
  • 무조건적으로 의존관계가 필요한 경우 사용하게 된다.
  • 👺 생성자가 딱 한개만 있으면 @Autowired를 생략해도 자동주입된다.
 
  • 예시
@Component public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; @Autowired public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } }
 
 
setter 주입
setter이라고 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 DI한다
  • 선택, 변경할 수 있는 의존관계에 사용한다.
  • 기존처럼 setter형식으로 사용하면 된다
 
  • 예시
@Component public class OrderServiceImpl implements OrderService { private MemberRepository memberRepository; private DiscountPolicy discountPolicy; @Autowired public void setMemberRepository(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Autowired public void setDiscountPolicy(DiscountPolicy discountPolicy) { this.discountPolicy = discountPolicy; } }
 
💡
자바빈 프로퍼티
JAVA에는 과거부터 필드의 값을 직접적으로 만지지 않고 Getter, Setter라는 메서드를 통해서 값을 일거나 수정하는 규칙을 만들었다. 해당 내용이 자바 빈 프로퍼티이다.
 
 
필드 주입
말 그대로 필드에 직접적으로 주입하는 방법이다.
  • 간단하지만 스프링 DI 프레임워크가 없으면 무용지물이다.
  • 거의 사용하지 않는다
 
@Autowired private DiscountPolicy discountPolicy;
  • 당연하게도 자바 빈 내부에서만 작동한다.
 
 
그냥 생성자 주입을 쓰는 이유
  1. DI는 한번 설계되면 거의 변하지 않는다.
  1. 수정자 주입(SETTER 주입)을 선택하면 Setter method를 public으로 열어두어야 하는데 보동 필드값은 private으로 설정되고 setter을 통해 주입하긴 하지만 변경을 지양해야 할 메서드를 열어두는 것은 좋은것이 아니다.
  1. final 키워드를 사용할 수 있다.
@Component public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; @Autowired public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } //... }
  • final을 넣어서 무조건적으로 해당 의존성을 요구하도록 할 수 있다.
  • 즉 컴파일 단계에서 해당 코드의 에러를 잡을 수 있다.
 
 

3. 옵션 처리

스프링 빈이 없어도 동작해야 할 때가 있다. 하지만 Autowired만 사용하면 required 옵션의 기본 값이 TRUE로 되어있어서 주입 대상이 없으면 오류가 발생한다.
 
예시를 통해 알아보자
public class AutowiredTest { @Test void autowiredOptions() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class); ac.getBean(TestBean.class); } @Configuration static class TestBean { @Autowired(required = false) public void setNoBean(Member member) { System.out.println("member = " + member); } @Autowired public void setNoBean2(@Nullable Member member2) { System.out.println("member2 = " + member2); } @Autowired public void setNoBean3(Optional<Member> member3) { System.out.println("member3 = " + member3); } } }
  1. Autowired에 required=false : 자동 주입할 대상이 없으면 수정자 메서드 자체가 x
  1. Nullerble : 자동 주입할 대상이 없으면 null이 입력됨
  1. Optional : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.
 
 
정리
  1. 생성자 주입을 선택하자
  1. 기본적으로 생성자 주입을 선택하되, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하자.
 
 

4. 롬복

생성자로 DI를 수행하다보면 반복, 코드 길이가 길어지게 된다 그래서 나오게 된게 롬복이다.
 
세팅
  1. lombok 설치 및 설정
notion image
 
  1. enable annotation processing 설정
notion image
  • 외부 어노테이션 라이브러리가 컴파일 시 문제없이 작동하도록 설정하기 위해서 어노테이션 프로세싱을 활성화 해주어야 한다.
 
  1. build.gradle 설정
의존성 추가 항목 compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok'
 
//lombok 설정 configurations { compileOnly { extendsFrom annotationProcessor } }
해당 코드는 lombok 라이브러리를 사용하기 위한 설정입니다.
compileOnly 구성요소에 annotationProcessor를 추가해서 사용하게 됩니다. annotationProcessor는 컴파일 시점에 사용되는 어노테이션 프로세서를 의미합니다. 이를 추가해주면 lombok 라이브러리가 제공하는 어노테이션을 사용할 수 있습니다.
 
사용
  1. Getter, Setter 자동 사용
@Getter @Setter public class HelloLombok { private String name; private int age; public static void main(String[] args) { HelloLombok helloLombok = new HelloLombok(); helloLombok.getName(); helloLombok.setName("lombok is assume"); System.out.println(helloLombok.getName()); } }
  • @Getter, @Setter 어노테이션을 설정해주면 lombok이 getter과 setter메서들을 자동적으로 설정해 준다.
 
 
  1. 의존성 주입 자동화
@Component @RequiredArgsConstructor public class MemberServiceImpl implements MemberService
 
//롬복이 알아서 만들어줌 // @Autowired // public MemberServiceImpl(MemberRepository memberRepository) { // this.memberRepository = memberRepository; // }
  • 필드를 final로 설정하고 RequiredArgsConstructor어노테이션을 붙여주면 자동적으로 lombok이 생성자 의존성 주입을 해준다.
 
 

5. 자동 의존관계 주입 중 빈이 겹칠때

Autowired 필드 명 매칭
  • Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가적으로 매핑한다.
@Autowired private DiscountPolicy discountPolicy to private DiscountPolicy rateDiscountPolicy
  • 타입 매칭
  • 타입 매칭 결과가 2개이상이면 필드 명으로 빈 이름 매칭
 
 
Quilifier 사용
추가로 구분할 수 있는 옵션을 붙여주는 방법이다.
@Qualifier("mainDiscountPolicy") public class RateDiscountPolicy implements DiscountPolicy
@Qualifier("subDiscountPolicy") public class StaticDiscountPolicy implements DiscountPolicy
 
이후 호출할 때에는 이런식으로 호출하고
@Autowired public DiscountPolicy setDiscountPolicy(@Qualifier("이름") DiscountPolicy discountPolicy)
에러의 경우에는
  • @Quilifier 끼리 매칭
  • 빈 이름 매칭
  • 이후 에러 발생한다.
 
Quilifier 사용 - 인터페이스를 생성해서 오류를 잡기
@Qualifier("어쩌구 저쩌구")
  • 만약 여기서 오타가 나게 되면 컴파일 시점에 에러가 발생하지 않는다.
  • 따라서 어노테이션을 새로 생성해서 에러를 컨트롤 할 수 있다.
 
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Qualifier("mainDiscountPolicy") public @interface MainDiscountPolicy { }
 
 
Primary 사용
@Primary 를 @Component를 선언해서 Bean을 등록할 때 선언해주면 @Primary가 우선권을 가진다.
@Component @Primary public class RateDiscountPolicy implements DiscountPolicy {} @Component public class FixDiscountPolicy implements DiscountPolicy {}
 
 

6. 모든 빈의 조회가 필요할 때

만약 할인 정책에 대하여 클라이언트가 할인의 종류를 선택할 수 있다고 가정하면, 주문에 있어서 할인 서비스를 선택할 수 있어야 한다. 해당 코드는 다음과 같다.
public class AllBeanTest { @Test void test() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, NewDiscountService.class); NewDiscountService newDiscountService = ac.getBean(NewDiscountService.class); Member member = new Member(1L, "userA", Grade.VIP); int discountPrice = newDiscountService.discount(member, 10000, "fixDiscountPolicy"); //vip이고 만원이면 Assertions.assertThat(discountPrice).isEqualTo(1000); } @Test void findAllBean(){ AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, NewDiscountService.class); // 둘다 등록 } static class NewDiscountService{ private final Map<String, DiscountPolicy> policyMap; private final List<DiscountPolicy> policyList; public NewDiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policyList) { this.policyMap = policyMap; this.policyList = policyList; System.out.println("policyMap = " + policyMap); System.out.println("policyList = " + policyList); } public int discount(Member member, int price, String discountNameCode) { DiscountPolicy discountPolicy = policyMap.get(discountNameCode); return discountPolicy.discount(member, price); } } }
  • 여기서 AutoAppConfig는 컴포넌트 스캔을 수행하는 설정파일이다.
  • policy들을 Map에 넣고 get 형식으로 할인정책을 가져오면 된다.
  • 정확히 말하면 map의 키에 스프링 빈들의 이름이 들어가고 그 값으로 DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담을 수 있다.
 
💡
스프링 컨테이너를 생성하면서 스프링 빈 등록하기
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, NewDiscountService.class); // 둘다 등록
  • 스프링 컨테이너를 생성하고 클래스 정보를 파라미터로 넘기면서 해당 클래스를 자동으로 스프링 빈으로 등록한다.